package cn.iocoder.yudao.module.trade.service.drainage;

import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
import cn.iocoder.yudao.module.infra.api.config.dto.ConfigDTO;
import cn.iocoder.yudao.module.trade.api.drainage.dto.CachePostOrderCountDto;
import cn.iocoder.yudao.module.trade.controller.admin.orderpostback.vo.OrderPostBackSaveReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.orderpostback.OrderPostBackDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.orderpostconfig.OrderPostConfigDO;
import cn.iocoder.yudao.module.trade.enums.orderpostback.OrderPostBackConfigTypesEnum;
import cn.iocoder.yudao.module.trade.enums.orderpostback.TradeOrderPostBackStatusEnum;
import cn.iocoder.yudao.module.trade.service.orderpostback.OrderPostBackService;
import cn.iocoder.yudao.module.trade.service.orderpostconfig.OrderPostConfigService;
import com.alibaba.fastjson.JSONObject;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class AbstractDrainageService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private OrderPostBackService orderPostBackService;
    @Resource
    private DrainageServiceFactory drainageServiceFactory;
    @Resource
    private OrderPostConfigService orderPostConfigService;
    @Resource
    private ConfigApi configApi;

    private static final String MEMBER_DRAINAGE_CACHE_KEY = "member:drainage:%d";
    private static final String MEMBER_DRAINAGE_POST_CACHE_KEY = "drainage:posted:%s";
    private static final String POST_START_SIGNAL_KEY = "post.config.start.limit";
    private static final String POST_DRAINAGE_TYPE_ENABLE = "post.drainage.type.%d";

    public void cacheMemberDrainageFlag(Long userId, JSONObject uriQuery) {
        stringRedisTemplate.opsForValue().set(String.format(MEMBER_DRAINAGE_CACHE_KEY, userId), uriQuery.toJSONString());
    }

    public boolean drainagePostBack(TradeOrderDO orderDO) {
        if (Objects.nonNull(orderDO.getUserId()) && orderDO.getUserId() > 0) {
            String uriQuery = getMemberDrainageCacheKey(orderDO.getUserId());
            if (StringUtils.isNoneEmpty(uriQuery)) {
                Map<String, String> params = parseQuery(uriQuery);
                if (params.containsKey("drainageCode")) {
                    String drainageCode = params.get("drainageCode");
                    OrderPostConfigDO configDO = orderPostConfigService.queryByDrainageCode(drainageCode);
                    if (configDO == null) {
                        log.warn("回传配置未找到: {}", params);
                        return false;
                    }
                    DrainageService drainageService = drainageServiceFactory.getByDrainageType(configDO.getDrainageType());
                    if (drainageService != null) {
                        if (!enableDrainageType(drainageService.getDrainageType())) {
                            log.warn("回传推送开关关闭: {}", drainageService.getDrainageType());
                            return true;
                        }
                        try {
                            List<OrderPostConfigDO> configs = Collections.singletonList(configDO);

                            CachePostOrderCountDto cacheCount = getPostOrderCount(drainageCode);
                            boolean send = effectivePostOrder(orderDO, configs, drainageService.getDrainageType(), cacheCount);
                            cacheCount.setOrderCount(cacheCount.getOrderCount() + 1);
                            if (send) {
                                cacheCount.setPostCount(cacheCount.getPostCount() + 1);
                            }
                            cachePostOrderCount(cacheCount);

                            drainageService.processOrder(orderDO, params, configDO, send);
                        } catch (Exception e) {
                            log.error("处理回传订单失败, id: {}, {}", orderDO.getId(), e.getMessage());
                        }
                    }
                }
            }
        }
        return true;
    }

    public boolean unPayOrderPostBack(TradeOrderDO orderDO) {
        if (Objects.nonNull(orderDO.getUserId()) && orderDO.getUserId() > 0) {
            String uriQuery = getMemberDrainageCacheKey(orderDO.getUserId());
            if (StringUtils.isNoneEmpty(uriQuery)) {
                Map<String, String> params = parseQuery(uriQuery);
                if (params.containsKey("drainageCode")) {
                    String drainageCode = params.get("drainageCode");
                    OrderPostConfigDO configDO = orderPostConfigService.queryByDrainageCode(drainageCode);
                    if (configDO == null) {
                        log.warn("回传配置未找到: {}", params);
                        return false;
                    }
                    DrainageService drainageService = drainageServiceFactory.getByDrainageType(configDO.getDrainageType());
                    if (drainageService != null) {
                        if (!enableDrainageType(drainageService.getDrainageType())) {
                            log.warn("回传推送开关关闭: {}", drainageService.getDrainageType());
                            return true;
                        }
                        try {
                            drainageService.unPayOrderPost(orderDO, params, configDO);
                        } catch (Exception e) {
                            log.error("处理回传待订单失败, id: {}, {}", orderDO.getId(), e.getMessage());
                        }
                    }
                }
            }
        }
        return true;
    }

    protected boolean effectivePostOrder(TradeOrderDO orderDO, List<OrderPostConfigDO> configs, Integer drainageType, CachePostOrderCountDto cacheCount) {
        List<OrderPostConfigDO> enableConfigs = configs.stream()
                .filter(item -> item.getDrainageType().equals(drainageType))
                .collect(Collectors.toList());

        if (CollectionUtils.isEmpty(enableConfigs)) {
            return true;
        }

        ConfigDTO configDTO = configApi.getConfigByKey(POST_START_SIGNAL_KEY);
        if (configDTO != null && Integer.parseInt(configDTO.getValue()) >= cacheCount.getPostCount()) {
            return true;
        }

        for (OrderPostConfigDO config : enableConfigs) {
            // config 80  percent 90 -> false
            if (OrderPostBackConfigTypesEnum.PERCENT.getType().equals(config.getConfigType())) {
                if (config.getConfigValue() < cacheCount.getPercent()) {
                    return false;
                }
            // config 49  payPrice 45 -> false
            } else if (OrderPostBackConfigTypesEnum.LESS_THEN.getType().equals(config.getConfigType())) {
                if (config.getConfigValue() > orderDO.getPayPrice()) {
                    return false;
                }
            // config 149 payPrice 150 -> false
            } else if (OrderPostBackConfigTypesEnum.MORE_THEN.getType().equals(config.getConfigType())) {
                if (config.getConfigValue() < orderDO.getPayPrice()) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean enableDrainageType(Integer drainageType) {
        return Optional.ofNullable(configApi.getConfigByKey(String.format(POST_DRAINAGE_TYPE_ENABLE, drainageType)))
                .map(ConfigDTO::getValue).map(Boolean::parseBoolean).orElse(Boolean.TRUE);
    }

    public String getMemberDrainageCacheKey(Long userId) {
        return stringRedisTemplate.opsForValue().get(String.format(MEMBER_DRAINAGE_CACHE_KEY, userId));
    }

    public CachePostOrderCountDto getPostOrderCount(String drainageCode) {
        String value = stringRedisTemplate.opsForValue().get(String.format(MEMBER_DRAINAGE_POST_CACHE_KEY, drainageCode));
        if (StringUtils.isNoneEmpty(value)) {
            return JSONObject.parseObject(value, CachePostOrderCountDto.class);
        } else {
            return new CachePostOrderCountDto(drainageCode);
        }
    }

    public void cachePostOrderCount(CachePostOrderCountDto orderCount) {
        long cacheSecond = diffSecondsBySecondDay();
        log.info(String.format(" - cache: %s; cacheSecond: %d", orderCount, cacheSecond));
        stringRedisTemplate.opsForValue().set(
                String.format(MEMBER_DRAINAGE_POST_CACHE_KEY, orderCount.getDrainageCode()),
                JSONObject.toJSONString(orderCount),
                cacheSecond, TimeUnit.SECONDS
                );
    }

    private long diffSecondsBySecondDay() {
        LocalDateTime futureTime = LocalDateTime.now().plusDays(1).toLocalDate().atStartOfDay();
        return ChronoUnit.SECONDS.between(LocalDateTime.now(), futureTime);
    }

    protected Map<String, String> parseQuery(String query) {
        Map<String, String> queryPairs = new LinkedHashMap<>();
        JSONObject params = JSONObject.parseObject(query);
        for (String key: params.keySet()) {
            queryPairs.put(key, params.getString(key));
        }
        return queryPairs;
    }


}
